#include "net.h"
#include "icmp.h"
#include "ip.h"

/**
 * @brief 发送icmp响应
 * 
 * @param req_buf 收到的icmp请求包
 * @param src_ip 源ip地址
 */
static void icmp_resp(buf_t *req_buf, uint8_t *src_ip)
{
    // Step1: 封装报头和数据
    buf_t txbuf;
    buf_init(&txbuf, req_buf->len);
    buf_copy(&txbuf, req_buf, req_buf->len);
    icmp_hdr_t *hdr = (icmp_hdr_t *)(txbuf.data);
    icmp_hdr_t *req_buf_hdr = (icmp_hdr_t *)(req_buf->data);
    hdr->type = ICMP_TYPE_ECHO_REPLY;
    hdr->code = req_buf_hdr->code; 
    hdr->id16 = req_buf_hdr->id16;
    hdr->seq16 = req_buf_hdr->seq16;

    // Step2: 填写校验和
    hdr->checksum16 = 0;
    //hdr->checksum16 = swap16(checksum16((uint16_t *)(txbuf.data), txbuf.len));
    hdr->checksum16 = checksum16((uint16_t *)(txbuf.data), txbuf.len);

    // Step3: 使用ip_out发送
    printf("\nicmp_resp: send ICMP response msg\n");
    ip_out(&txbuf, src_ip, NET_PROTOCOL_ICMP);
}

/**
 * @brief 处理一个收到的数据包
 * 
 * @param buf 要处理的数据包
 * @param src_ip 源ip地址
 */
void icmp_in(buf_t *buf, uint8_t *src_ip)
{
    // 竟然不用检查校验和
    // Step4: 报头检查
    int icmp_hdr_size = sizeof(icmp_hdr_t);
    if(buf->len < icmp_hdr_size){
        printf("\nicmp_in: len(buf) < size(icmp_hdr)\n");
        return;
    }
    // Step2: 检查是否为回显请求
    icmp_hdr_t *hdr = (icmp_hdr_t *)(buf->data);
    if(hdr->type == ICMP_TYPE_ECHO_REQUEST){
        printf("\nicmp_in: icmp type is ECHO_REQUEST\n");
        icmp_resp(buf, src_ip);
    }
}

/**
 * @brief 发送icmp不可达
 * 
 * @param recv_buf 收到的ip数据包
 * @param src_ip 源ip地址
 * @param code icmp code，协议不可达或端口不可达
 */
void icmp_unreachable(buf_t *recv_buf, uint8_t *src_ip, icmp_code_t code)
{
    // Step1: 初始化txbuf, 填写数据部分
    buf_t txbuf;
    int size = ((ip_hdr_t *)(recv_buf->data))->hdr_len * IP_HDR_LEN_PER_BYTE + 8;
    buf_init(&txbuf, size);
    memcpy(txbuf.data, recv_buf->data, size);

    // Step2: 增加并填写ICMP报头
    size = sizeof(icmp_hdr_t);
    buf_add_header(&txbuf, size);
    icmp_hdr_t *hdr = (icmp_hdr_t *)(txbuf.data);
    hdr->type = ICMP_TYPE_UNREACH;
    hdr->code = code;
    hdr->id16 = 0;  // unreachable不用标识符和序列号,目前所知只有ICMP查询才需要用到
    hdr->seq16 = 0;
    hdr->checksum16 = 0;
    //hdr->checksum16 = swap16(checksum16((uint16_t *)(txbuf.data),txbuf.len));
    hdr->checksum16 = checksum16((uint16_t *)(txbuf.data),txbuf.len);

    // Step3: ip_out发送
    printf("\nicmp_unreachable: send ICMP unreachable response msg\n");
    ip_out(&txbuf, src_ip, NET_PROTOCOL_ICMP);
}

/**
 * @brief 初始化icmp协议
 * 
 */
void icmp_init(){
    net_add_protocol(NET_PROTOCOL_ICMP, icmp_in);
}